/**
 *Copyright (c) 2024 watereyes
 * safetymanagement is licensed under Mulan PubL v2.
 * You can use this software according to the terms and conditions of the Mulan
 * PubL v2.
 * You may obtain a copy of Mulan PubL v2 at:
 *          http://license.coscl.org.cn/MulanPubL-2.0
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PubL v2 for more details.
 */
package com.koron.common.service.impl;

import com.koron.common.Tools;
import com.koron.common.bean.StaffBean;
import com.koron.common.dfs.FastDFSClientWrapper;
import com.koron.common.domain.AttachmentBean;
import com.koron.common.domain.MultipartFileParam;
import com.koron.common.mapper.AttachmentMapper;
import com.koron.common.service.api.AttachmentUploadService;
import com.koron.common.util.DocConverterUtils2;
import net.coobird.thumbnailator.Thumbnails;
import net.coobird.thumbnailator.geometry.Positions;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.koron.ebs.mybatis.ADOConnection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.FileSystemUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.*;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 附件上传
 *
 * @author shexiaoxin
 * @since 2020-03-17
 */
@Service
public class AttachmentUploadServiceImpl implements AttachmentUploadService {

    private static final Logger logger = LoggerFactory.getLogger(AttachmentUploadServiceImpl.class);

    private List<String>  PICFORMAT = Arrays.asList(new String[]{"bmp", "jpg", "jpeg", "png", "tif", "gif"});

    private List<String> videoFormatList;

    // 保存文件的根目录
    private Path rootPath;

    @Value("${breakpoint.upload.dir}")
    private String finalDirPath;

    @Value("${video.upload.dir}")
    private String videoDirPath;

    @Autowired
    private FastDFSClientWrapper clientWrapper;

    @Autowired
    private DocConverterUtils2 docConverterUtils;

    private final ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);

    @Autowired
    public AttachmentUploadServiceImpl(@Value("${breakpoint.upload.dir}") String location, @Value("${video.upload.dir}") String videoLocation) {
        try {
            this.videoFormatList = Arrays.asList(new String[] { "mp4", "m2v", "mkv", "rmvb", "wmv", "avi", "flv", "mov", "m4v"});
            this.rootPath = Paths.get(location);
            if (!Files.exists(this.rootPath))
                Files.createDirectory(this.rootPath);
        } catch (FileAlreadyExistsException e) {
            logger.error("文件夹已存在"+e.getMessage(),e);
        } catch (IOException e) {
            logger.error("初始化文件夹失败。"+e.getMessage(), e);
        }

        try {
            if (!Files.exists(Paths.get(videoLocation))){
                Files.createDirectory(Paths.get(videoLocation));
            }
        } catch (FileAlreadyExistsException e) {
            logger.error("视频存储文件夹已存在"+e.getMessage(),e);
        } catch (IOException e) {
            logger.error("初始化视频存储文件夹失败。"+e.getMessage(), e);
        }
    }

    /**
     * 附件上传
     *
     * @return Map(删除条数, 插入条数)
     */
    public Map<String, Integer> uploadAttach(StaffBean userBean, String deleteId, String businessId, MultipartFile[] files) {
        //先插入，后删除
        Map<String, Integer> map = new HashMap();
        Integer uploadNum = this.uploadFiles(userBean, businessId, files);
        Integer delectNum = this.deleteFiles(userBean, businessId, deleteId);
        map.put("insertNum", uploadNum);
        map.put("deleteNum", delectNum);
        return map;
    }

    public Integer uploadFiles(StaffBean userBean, String businessId, MultipartFile[] files) {
        return this.uploadFilesByModel(userBean,businessId,files,null);
    }

    /**
     * 批量上传附件
     *
     * @return 附件List
     */
    public Integer uploadFilesByModel(StaffBean userBean, String businessId, MultipartFile[] files,String attModel) {
        //插入数据
        List<AttachmentBean> attachList = new ArrayList<>();
        try {
            if (null != files && files.length > 0) {
                for (MultipartFile file : files) {
                    ByteArrayOutputStream bout = null;
                    InputStream bin = null;
                    InputStream iconStream = null;
                    InputStream fileStream = null;
                    try {
                        String url = null;
                        String fileName = file.getOriginalFilename();
                        if (fileName.indexOf(".") < 0) {
                            return 1;//uniapp 框架删除附件文件没有后缀名异常 处理
                        }
                        String extName = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
                        if (PICFORMAT.contains(extName)) {
                            byte[] data = file.getBytes();
                            byte[] dupData = new byte[data.length];
                            System.arraycopy(data, 0, dupData, 0, data.length);
                            int height = -1;
                            int width = -1;
                            ByteArrayInputStream dupStream = null;
                            try {
                                dupStream = new ByteArrayInputStream(dupData);
                                BufferedImage src = ImageIO.read(dupStream);
                                height = src.getHeight();
                                width = src.getWidth();
                            } catch (IOException e) {
                                logger.error(e.getMessage(),e);
                            } finally {
                                try {
                                    if (dupStream != null) {
                                        dupStream.close();
                                    }
                                } catch (IOException e) {
                                    logger.error(e.getMessage(),e);
                                }
                            }
                            int len = height > width ? width : height;
                            fileStream = file.getInputStream();
                            iconStream = this.getClass().getClassLoader().getResourceAsStream("logoicon.png");
                            BufferedImage waterMarkImage = Thumbnails.of(iconStream).size((int)(len * 0.1), (int)(len * 0.1))
                                    .outputQuality(1.0f).keepAspectRatio(false).asBufferedImage();
                            bout = new ByteArrayOutputStream();
                            Thumbnails.of(fileStream).size(width, height)
                                    .watermark(Positions.TOP_LEFT, waterMarkImage, 0.6f)
                                    .toOutputStream(bout);
                            byte[] bytes = bout.toByteArray();
                            bin = new ByteArrayInputStream(bytes);
                            url = clientWrapper.uploadFile(bin, bytes.length, extName);
                        } else {
                            url = clientWrapper.uploadFile(file);
                        }
                        if (StringUtils.isNotBlank(url)) {
                            String filename = file.getOriginalFilename();
                            String attachmentName = filename.substring(0, filename.lastIndexOf("."));
                            String extension = filename.substring(filename.lastIndexOf(".") + 1);
                            AttachmentBean attach = new AttachmentBean();
                            attach.setId(Tools.getCode32());
                            attach.setAttachmentName(attachmentName);
                            attach.setExtension(extension);
                            attach.setAttachmentUrl(url);
//                          attach.setPdfUrl(pdfUrl);
                            attach.setBusinessId(businessId);
                            attach.setAttModel(attModel);
                            attach.setUploaderCode(userBean.getCode());
                            attach.setUploaderName(userBean.getName());
                            attach.setFile(file);
                            attachList.add(attach);
                        }
                    } catch (IOException e) {
                        logger.error(e.getMessage(),e);
                    } finally {
                        try {
                            if (bin != null) {
                                bin.close();
                            }
                        } catch (IOException e) {
                            logger.error(e.getMessage(),e);
                        }
                        try {
                            if (bout != null) {
                                bout.close();
                            }
                        } catch (IOException e) {
                            logger.error(e.getMessage(),e);
                        }
                        try {
                            if (iconStream != null) {
                                iconStream.close();
                            }
                        } catch (IOException e) {
                            logger.error(e.getMessage(),e);
                        }
                        try {
                            if (fileStream != null) {
                                fileStream.close();
                            }
                        } catch (IOException e) {
                            logger.error(e.getMessage(),e);
                        }
                    }
                }
                //将附件信息批量插入数据库
                if (!attachList.isEmpty()) {
                    Integer insertNum = ADOConnection.runTask(factory -> factory.getMapper(AttachmentMapper.class).insertAttachment(attachList), Integer.class);
                    logger.info("----- upload document insertNum = " + insertNum);
                    //附件在线预览线程转换
                    if (insertNum != 0) {
                        this.uploadThread(attachList);
                    }
                    return insertNum;
                }
            }
        } catch (Exception e) {
            //失败回滚，删除已上传文件
            if (attachList != null && !attachList.isEmpty()) {
                for (AttachmentBean attachmentBean : attachList) {
                    clientWrapper.deleteFile(attachmentBean.getAttachmentUrl());
                    //clientWrapper.deleteFile(attachmentBean.getPdfUrl());
                }
            }
            logger.error(e.getMessage(),e);
        }
        return 0;
    }

    /**
     * 根据Id批量删除附件
     *
     * @return 附件List
     */
    public Integer deleteFiles(StaffBean userBean, String businessId, String deleteId) {
        // 删除数据
        if (StringUtils.isNotBlank(deleteId)) {
            if (businessId.split(",").length <= 0) {
                return 0;
            }
            //查询数据库信息
            List<AttachmentBean> attachmentBean = ADOConnection.runTask(factory ->
                            factory.getMapper(AttachmentMapper.class).getAttachmentById(deleteId, businessId)
                    , List.class);
            if (attachmentBean != null && !attachmentBean.isEmpty()) {
                for (AttachmentBean attach : attachmentBean) {
                    clientWrapper.deleteFile(attach.getAttachmentUrl());
                    clientWrapper.deleteFile(attach.getPdfUrl());
                    removeVideo(attach.getAttachmentUrl());
                }
                //批量删除
                Integer deleteNum = ADOConnection.runTask(factory ->
                                factory.getMapper(AttachmentMapper.class).deleteAttachment(deleteId, businessId)
                        , Integer.class);
                logger.info("附件删除记录：" + deleteNum + "条");
                return deleteNum;
            }
        }
        return 0;
    }


    private void removeVideo(String filePath) {
        try {
            String extName = filePath.substring(filePath.lastIndexOf(".") + 1);
            if (videoFormatList.contains(extName.toLowerCase())) {
                Path path = Paths.get(this.videoDirPath, filePath.substring(filePath.lastIndexOf("/") + 1));
                Files.deleteIfExists(path);
            }
        } catch (IOException e) {
            logger.error(e.getMessage(),e);
        }
    }

    /**
     * 查询附件列表
     *
     * @return 附件List
     */
    public List<AttachmentBean> getAttachListByModel(String businessId, String attModel) {
        List<AttachmentBean> attachmentBeans = ADOConnection.runTask(factory ->
                        factory.getMapper(AttachmentMapper.class).getAttachmentListByModel(businessId,attModel)
                , List.class);
        return attachmentBeans;
    }

    private void uploadThread(List<AttachmentBean> attachList) {
        fixedThreadPool.submit(
            () -> {
                logger.error("----- enter to document convert -----");
                    if (attachList != null && !attachList.isEmpty()) {
                        for (AttachmentBean attachmentBean : attachList) {
                            try {
                                logger.info("----- before convert -----");
                                String pdfUrl = docConverterUtils.converterUpdate(attachmentBean.getAttachmentUrl(), attachmentBean.getFile().getOriginalFilename());
                                logger.info("----- after convert pdfUrl = " + pdfUrl);
                                attachmentBean.setPdfUrl(pdfUrl);
                            } catch (Exception ex) {
                                logger.error("文件预览转换失败：" + attachmentBean.getId()+ ex.getMessage(),ex);
                            }
                        }
                        //批量更新数据库
                        ADOConnection.runTask(factory -> factory.getMapper(AttachmentMapper.class).updateAttachPdfUrl(attachList), Integer.class);
                    }
            }
        );
    }




    @Override
    public boolean uploadFileByMappedByteBuffer(StaffBean userBean, MultipartFileParam param) throws Exception {
        String fileName = param.getFilename();
        String uploadDirPath = finalDirPath + param.getSessionId() + File.separator + param.getIdentifier();
        String tempFileName = fileName + "_tmp";
        File tmpDir = new File(uploadDirPath);
        File tmpFile = new File(uploadDirPath, tempFileName);
        if (!tmpDir.exists()) {
            tmpDir.mkdirs();
        }

        RandomAccessFile tempRaf = new RandomAccessFile(tmpFile, "rw");
        FileChannel fileChannel = tempRaf.getChannel();

        //写入该分片数据
        long offset = param.getChunkSize() * param.getChunkNumber();
        byte[] fileData = param.getUpfile().getBytes();
        MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, offset, fileData.length);
        mappedByteBuffer.put(fileData);
        // 释放
        //FileMD5Util.freedMappedByteBuffer(mappedByteBuffer);
        fileChannel.close();

        if (checkAndSetUploadProgress(param, uploadDirPath) && renameFile(tmpFile, fileName)) {
            File newFile = new File(tmpDir, fileName);
            String filePath = storeFile(userBean, newFile, param);
            if (StringUtils.isNotBlank(filePath) && param.isFromTraining()) {
                String name = filePath.substring(filePath.lastIndexOf("/") + 1);
                String extName = filePath.substring(filePath.lastIndexOf(".") + 1);
                if (videoFormatList.contains(extName.toLowerCase())) {
                    //copy file
                    FileUtils.copyFile(newFile, new File(videoDirPath, name));
                }
            }
            FileSystemUtils.deleteRecursively(new File(finalDirPath + param.getSessionId()));
            logger.debug("upload complete! name=" + fileName);
            return true;
        }
        return false;
    }

    /**
     * 检查并修改文件上传进度
     *
     * @param param
     * @param uploadDirPath
     * @return
     * @throws IOException
     */
    private boolean checkAndSetUploadProgress(MultipartFileParam param, String uploadDirPath) throws IOException {
        String fileName = param.getFilename();
        File confFile = new File(uploadDirPath, fileName + ".conf");
        RandomAccessFile accessConfFile = new RandomAccessFile(confFile, "rw");
        //把该分段标记为 true 表示完成
        logger.debug("set part " + param.getChunkNumber() + " complete");
        accessConfFile.setLength(param.getTotalChunks());
        accessConfFile.seek(param.getChunkNumber());
        accessConfFile.write(Byte.MAX_VALUE);

        //completeList 检查是否全部完成,如果数组里是否全部都是(全部分片都成功上传)
        byte[] completeList = FileUtils.readFileToByteArray(confFile);
        byte isComplete = Byte.MAX_VALUE;
        for (int i = 0; i < completeList.length && isComplete == Byte.MAX_VALUE; i++) {
            //与运算, 如果有部分没有完成则 isComplete 不是 Byte.MAX_VALUE
            isComplete = (byte) (isComplete & completeList[i]);
            logger.debug("check part " + i + " complete?:" + completeList[i]);
        }

        accessConfFile.close();
        return isComplete == Byte.MAX_VALUE ? true : false;
    }

    /**
     * 文件重命名
     *
     * @param toBeRenamed   将要修改名字的文件
     * @param toFileNewName 新的名字
     * @return
     */
    public boolean renameFile(File toBeRenamed, String toFileNewName) {
        //检查要重命名的文件是否存在，是否是文件
        if (!toBeRenamed.exists() || toBeRenamed.isDirectory()) {
            logger.info("File does not exist: " + toBeRenamed.getName());
            return false;
        }
        String p = toBeRenamed.getParent();
        File newFile = new File(p + File.separatorChar + toFileNewName);
        //修改文件名
        return toBeRenamed.renameTo(newFile);
    }


    private String storeFile(StaffBean userBean, File file, MultipartFileParam param) throws Exception {
        FileInputStream fileInputStream = null;
        ByteArrayOutputStream bout = null;
        ByteArrayInputStream bin = null;
        InputStream iconStream = null;
        try {
            String url = null;
            String fileName = param.getFilename();
            String attachmentName = fileName.substring(0, fileName.lastIndexOf("."));
            String extName = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
            fileInputStream = new FileInputStream(file);
            if (PICFORMAT.contains(extName)) {
                Pair<Integer, Integer> pair = getWidthHeight(file);
                int width = pair.getKey();
                int height = pair.getValue();
                int len = height > width ? width : height;

                iconStream = this.getClass().getClassLoader().getResourceAsStream("logoicon.png");
                BufferedImage waterMarkImage = Thumbnails.of(iconStream).size((int)(len * 0.1), (int)(len * 0.1))
                        .outputQuality(1.0f).keepAspectRatio(false).asBufferedImage();
                bout = new ByteArrayOutputStream();
                Thumbnails.of(fileInputStream).size(width, height)
                        .watermark(Positions.TOP_LEFT, waterMarkImage, 0.6f)
                        .toOutputStream(bout);
                byte[] bytes = bout.toByteArray();
                bin = new ByteArrayInputStream(bytes);
                url = clientWrapper.uploadFile(bin, bytes.length, extName);
            } else {
                url = clientWrapper.uploadFile(fileInputStream, param.getTotalSize(), extName);
            }

            if (StringUtils.isNotBlank(url)) {
                AttachmentBean attachment = new AttachmentBean();
                attachment.setId(Tools.getCode32());
                attachment.setAttachmentName(attachmentName);
                attachment.setExtension(extName);
                attachment.setAttachmentUrl(url);
                attachment.setBusinessId(param.getBusinessId());
                attachment.setUploaderCode(userBean.getCode());
                attachment.setUploaderName(userBean.getName());
                attachment.setAttModel(param.getAttModel());
                List<AttachmentBean> attachmentList = new ArrayList<>();
                attachmentList.add(attachment);

                Integer count = ADOConnection.runTask(factory -> factory.getMapper(AttachmentMapper.class).insertAttachment(attachmentList), Integer.class);
                if (count > 0) {
                    fixedThreadPool.submit(
                            () -> {
                                try {
                                    String pdfUrl = docConverterUtils.converterUpdate(attachment.getAttachmentUrl(), fileName);
                                    attachment.setPdfUrl(pdfUrl);
                                } catch (Exception ex) {
                                    logger.error("文件预览转换失败：" + attachment.getId()+ex.getMessage(),ex);
                                }
                                //批量更新数据库
                                ADOConnection.runTask(factory -> factory.getMapper(AttachmentMapper.class).updateAttachPdfUrl(attachmentList), Integer.class);
                            }
                    );
                }
                return url;
            }
        } catch (FileNotFoundException e) {
            logger.error(e.getMessage(),e);
        } finally {
            try {
                if (bin != null) {
                    bin.close();
                }
            } catch (IOException e) {
                logger.error(e.getMessage(),e);
            }
            try {
                if (bout != null) {
                    bout.close();
                }
            } catch (IOException e) {
                logger.error(e.getMessage(),e);
            }
            try {
                if (iconStream != null) {
                    iconStream.close();
                }
            } catch (IOException e) {
                logger.error(e.getMessage(),e);
            }
            try {
                if (fileInputStream != null) {
                    fileInputStream.close();
                }
            } catch (IOException e) {
                logger.error(e.getMessage(),e);
            }
        }
        return null;
    }


    private Pair<Integer, Integer> getWidthHeight(File file) {
        BufferedImage src = null;
        int height = -1;
        int width = -1;
        try (InputStream is = new FileInputStream(file);) {
            src = ImageIO.read(is);
            height = src.getHeight();
            width = src.getWidth();
        } catch (Exception e) {
            logger.error(e.getMessage(),e);
        }
        return Pair.of(width, height);
    }

   /* @Override
    public void uploadFileRandomAccessFile(MultipartFileParam param) throws IOException {
        String fileName = param.getFilename();
        String tempDirPath = finalDirPath + param.getIdentifier();
        String tempFileName = fileName + "_tmp";
        File tmpDir = new File(tempDirPath);
        File tmpFile = new File(tempDirPath, tempFileName);
        if (!tmpDir.exists()) {
            tmpDir.mkdirs();
        }

        RandomAccessFile accessTmpFile = new RandomAccessFile(tmpFile, "rw");
        long offset = param.getChunkSize() * param.getChunkNumber();
        //定位到该分片的偏移量
        accessTmpFile.seek(offset);
        //写入该分片数据
        accessTmpFile.write(param.getUpfile().getBytes());
        // 释放
        accessTmpFile.close();

        if (checkAndSetUploadProgress(param, tempDirPath)) {
            boolean flag = renameFile(tmpFile, fileName);
            logger.debug("upload complete !!" + flag + " name=" + fileName);
        }
    }*/

}